Calcul de complexité, pour quoi faire ?

Nous allons dans cette partie introduire la notion de complexité algorithmique, sorte de quantification de la performance d'un algorithme.

L'objectif premier d'un calcul de complexité algorithmique est de pouvoir comparer l’efficacité d’algorithmes résolvant le même problème. Dans une situation donnée, cela permet donc d'établir lequel des algorithmes disponibles est le plus optimal.

Ce type de question est primordial, car pour des données volumineuses la différence entre les durées d'exécution de deux algorithmes ayant la même finalité peut être de l'ordre de plusieurs jours.

Les règles que nous utiliserons pour comparer et évaluer les algorithmes devront respecter certaines contraintes très naturelles. On requerra principalement qu'elles ne soient pas tributaires des qualités d'une machine ou d'un choix de technologie.

Nous allons donc effectuer des calculs sur l’algorithme en lui même, dans sa version "papier". Les résultats de ces calculs fourniront une estimation du temps d’exécution de l’algorithme, et de la taille mémoire occupée lors de son fonctionnement.

Le coût (en temps) d'un algorithme est l'ordre de grandeur du nombre d'opérations arithmétiques ou logiques que doit effectuer un algorithme pour résoudre le problème auquel il est destiné.

Cet ordre de grandeur dépend évidemment de la taille $N$ des données en entrée.

On parlera de :

Il existe aussi un coût en mémoire d'un algorithme : c'est l'ordre de grandeur de la place qu'il faut réserver pour la bonne exécution de cet algorithme.
Cet ordre de grandeur dépend évidemment lui aussi de la taille $N$ des données en entrée.

Dans la suite de ce cours, vous travaillerez uniquement sur la complexité en temps et principalement avec des coûts constants, linéaires ou quadratiques.

Calcul de complexité

Règles générales

Pour calculer la complexité, nous allons devoir examiner chaque ligne de code et l'y attribuer un coût en temps.

Le coût ainsi obtenu n'aura pas d'unité, il s'agit d'un nombre d'opérations dont chacune aurait le même temps d'exécution : 1.

Les opérations qui vont devoir être comptabilisées auront les coûts suivant :

Déterminons le coût de la ligne de code suivante :
a = a + 1

la complexité, notée $T(n)$, est : $T(n)=1$ (pour l'affectation) $+1$ (pour l'accès à la mémoire $a$) $+1$ (pour l'addition) $=3$.

La coût de cet algorithme est dite constant. Ce sera le cas de tous les algorithmes avec $T(n)=a$ où $a$ est un réel.

On notera ce type de coût constant : $O(1)$.

Algorithmes sans structure de contrôle

Déterminer la complexité, notée $T(n)$, de cet algorithme écrit en python qui permet de convertir un temps en secondes en un temps en heure, minute et secondes.

1 def conversion(n: float) -> tuple:
2	h = n // 3600
3	m = (n - 3600*h) // 60
4	s = n % 60
5	return h, m, s

Pour le calcul de la complexité, on ne prendra en compte ni la ligne 1 ni la ligne 5.
Dans la suite, quand on s'intéressera à la complexité d'un algorithme écrit en Python, nous ne prêterons pas attention à la ligne de définition de la fonction ni à celle liée au return.

Code de déblocage de la correction :

exercice de renforcement

Algorithmes avec structure conditionnelle

La fonction suivante calcule $(-1)^n$ sans effectuer de produit mais en utilisant un test avec une alternative :

1 def puissanceMoinsUn(n: int) -> int:
2 	if n%2 == 0:
3 		res = 1
4 	else:
5 		res = -1
6	return res

Déterminer la complexité $T(n)$ de cet algorithme.

Code de déblocage de la correction :

La fonction suivante indique si l'année annee est bissextile ou non en renvoyant un booléen :

1 def est_bissextile(annee: int) -> bool:
2 	if annee % 400 == 0:
3 		res = True
4 	elif annee % 100 == 0:
5 		res = False
6	elif annee % 4 ==0:
7       res = True 
8   else: 
9       res = False 
10  return res
  1. Quel est le coût (en temps) lorsque la fonction prend comme argument une année multiple de 400 comme 2000 ?

  2. Quel est le coût (en temps) lorsque la fonction prend comme argument une année multiple de 100 sans l'être de 400 comme 2100 ?

  3. Quel est le coût (en temps) lorsque la fonction prend comme argument une année multiple de 4 sans l'être de 100 ni de 400 comme 2020 ?

  4. Quel est le coût (en temps) lorsque la fonction prend comme argument une année non multiple de 4 comme 2021 ?

  5. Quel est le coût dans le pire des cas, c'est-à-dire dans le cas où le coût d'exécution est le plus grand ?
    Quel est le coût dans le meilleur cas ?

Code de déblocage de la correction :

exercice de renforcement

Algorithmes avec structure itérative

Cette fonction utilise une structure for pour calculer la somme des $n$ premiers entiers

1 def sommeEntiers1(n: int) -> int:
2 	somme = 0
3 	for i in range(n+1):
4 		somme += i
5 	return somme

Déterminer la complexité $T(n)$ de cet algorithme.

Code de déblocage de la correction :

exercice de renforcement

La complexité de cet algorithme est dite linéaire. Ce sera le cas de tous les algorithmes avec $T(n)=an+b$ où $a$ et $b$ sont des réels.

On notera ce type de complexité linéaire : $O(n)$.

Ceux et celles suivant la spécialité mathématique ont vu ou vont voir l'égalité suivante, vraie pour tout entier naturel $n$ non nul :

$$1+2+...+(n-2)+(n-1)+n=\dfrac{n(n+1)}{2}$$

Source : Le kangourou des mathématiques

En utilisant cette égalité, voici une seconde fonction sommeEntiers2 renvoyant la somme de tous les nombres entiers jusqu'à $n$, $n$ étant l'argument entier de cette fonction :

1 def sommeEntiers2(n: int) -> int:
2 	somme = n*(n+1)/2
3 	return somme
  1. Déterminer la complexité $T(n)$ de cet algorithme.

  2. Que pouvez-vous dire de la comparaison des coûts des algorithmes liées aux fonctions sommeEntiers1 et sommeEntiers2 lorsque $n$ devient très grand ?

Code de déblocage de la correction :

La complexité en temps sert à savoir quel algorithme il est préférable d'exécuter (sans prise en compte de la mémoire nécessaire) pour obtenir un résultat.

Algorithmes avec deux structures itératives imbriquées

Un réseau social contient $n$ membres. L'entreprise gérant ce réseau stocke dans un tableau Tab1 de taille $n\times n$, le nombre de messages envoyés envoyés entre chacun des membres du réseau. La case Tab1[i][j] comptabilise le nombre de messages envoyés du membre référencé par le numéro i vers le membre référencé par le numéro j.

L'entreprise veut connaître le nombre de messages déjà envoyés sur son réseau social en utilisant les données de son tableau.

Voici l'algorithme, écrit en langage Python, qui a été proposé :

1 def Somme_messages(Tab1) -> int:
2 	somme = 0
3 	for i in range(n):
4 		for j in range(n):
5 			somme = somme + Tab1[i][j]
6	return somme
  1. Pour une valeur de $i$ fixée, déterminer le coût des deux lignes suivantes, en admettant que le coût d'accès à Tab1[i][j] est de 1 :

    4 		for j in range(n):
    5 			somme = somme + Tab1[i][j]
  2. Déterminer la complexité $T(n)$ de cet algorithme sachant que le nombre de membres est de $n$.

Code de déblocage de la correction :

exercice de renforcement

La complexité de cet algorithme est dite quadratique. Ce sera le cas de tous les algorithmes avec $T(n)=an^2+bn+c$ où $a$, $b$ et $c$ sont des réels.

On notera ce type de complexité quadratique : $O(n^2)$.

Un réseau social contient $n$ membres. L'entreprise gérant ce réseau stocke dans un tableau Tab2 de taille $n\times n$, le nombre d'"amis" communs entre deux membres distincts du réseau. La case Tab2[i][j], avec $i\ne j$, comptabilise le nombre d'amis commun entre le membre référencé par le numéro i et le membre référencé par le numéro j.

L'entreprise veut connaître le nombre d'amis communs sur son réseau social en utilisant les données de son tableau.

Voici l'algorithme, écrit en langage Python, qui a été proposé :

1 def Somme_amis(Tab2) -> int:
2 	somme = 0
3 	for i in range(n):
4 		for j in range(i+1, n):
5 			somme = somme + Tab2[i][j]
6	return somme
  1. Compléter le tableau suivant :

    Valeur de $i$ Nombre d'itérations liées à range(i+1, n)
    0 ...
    1 ...
    2 ...
    ... ...
    $i$ ...
    ... ...
    $n-2$ ...
    $n-1$ ...
  2. Pour une valeur de $i$ fixée, déterminer le coût des deux lignes suivantes :

    
    4 		for j in range(i+1,n):
    5 			somme = somme + Tab2[i][j]
    
  3. Déterminer la complexité $T(n)$ de cet algorithme sachant que le nombre de membres est de $n$.

Code de déblocage de la correction :

Voici un algorithme, écrit en langage Python, qui permet de rechercher le maximum d'une liste L d'entiers.

1 def get_maximum(L: list) -> int:
2 	maxi = L[0]
3   n = len(L)
4 	for i in range(1, n):
5 		if L[i] > maxi:
6           maxi = L[i]
7	return maxi
  1. En considérons que le coût d'accès à la valeur de l'élément d'index $i$ de la liste $L$ vaut 1, quelle que soit la valeur de $i$, déterminer le coût de cet algorithme dans le meilleur cas : celui où le maximum se trouve en première position.

  2. Déterminer le coût de cet algorithme dans le pire des cas : celui où les éléments de la liste sont rangés par ordre croissant.

Code de déblocage de la correction :

Le coût (en temps) d'un algorithme ou complexité en temps est l'ordre de grandeur du nombre d'opérations arithmétiques ou logiques, du nombre d'accès en mémoire et d'affectation qu'on' doit effectuer lors de l'exécution d'un algorithme pour résoudre le problème auquel il est destiné.

Cet ordre de grandeur peut dépendre de la taille $n$ des données en entrée, les principaux coûts au programme de première NSI sont :

  1. coût constant s'il ne dépend pas de la taille de $n$, noté désormais $O(1)$ : si $T(n)=\text{constante}$ alors on note $T(n)=O(1)$.

  2. coût linéaire si le coût est proche d'être proportionnel à la taille $n$ lorsque $n$ est très grand ; ce coût est désormais noté $O(n)$ : si le temps d'exécution $T(n)$ est majorée par $a\times n+b$, avec $a$ et $b$ deux constantes réelles, alors on note $T(n)=O(n)$.

  3. coût quadratique si le coût est proche d'être proportionnel au carré de la taille $n$ lorsque $n$ est très grand ; ce coût est désormais noté $O(n^2)$ : si le temps d'exécution $T(n)$ est majorée par $a\times n^2+b\times n +c$, avec $a$, $b$ et $c$ trois constantes réelles, alors on note $T(n)=O(n^2)$.

On peut utiliser cette complexité en temps pour privilégier un algorithme à un autre.

La complexité algorithmique est une notion qui peut être plus complexe que ce que nous venons de voir, nous n'irons pas plus loin dans ce cours. Nous retravaillerons cette notion dans les cours d'algorithme de première et de terminale.
Cependant, ceux et celles intéressé.e.s pour prolonger peuvent regarder la partie suivante.

Compléments

Mesure du temps d'exécution

Pour chronométrer le temps d'exécution d'une fonction, vous pouvez utiliser le module timeit.
Celui-ci contient la méthode import default_timer qui calcule le temps d’exécution relatif à une fonction donnée. Voici comment on peut utiliser cette méthode :

from timeit import default_timer as timer 

start = timer()
# tâches dont on veut mesurer le temps d'exécution.
end = timer()
print(end - start) # affichage du temps écoulé    

Voici des fonctions donnant la liste des décompositions possibles d'un entier naturel $n$ comme somme de deux carrés :

1    def decompo_carres1(n) :
2   l_res = []
3   for i in range(n+1):
4       for j in range(n+1):
5           if i**2+j**2 == n:
6               l_res.append((i, j))
7   return l_res         
from math import sqrt, floor
1   def decompo_carres2(n):
2       l_res = []
3       fin = floor(sqrt(n)) 
4       for i in range(fin+1):
5           j = sqrt(n - i**2)      # nombre réel vérifiant i^2+j^2=n connaissant i et n
6           if j == floor(j):   # cas où j est un nombre entier
7               l_res.append((i, int(j)))
8       return l_res         

Lorsque $x$ est un nombre réel positif, floor(x) renvoie la partie entière de $x$, c'est-à-dire la partie devant la virgule dans l'écriture décimale de $x$.
Lorsque $x$ est un nombre réel positif, sqrt(x) renvoie la racine carrée de $x$.

  1. Utiliser le module default_timer pour estimer le temps d'exécution de chacune de ces fonctions en prenant pour argument 1000 puis 10000. Comparer.

  2. Estimer le coût $T_1(n)$ de l'algorithme correspondant à la fonction decompo_carres1.

    L'ajout d'un terme en fin de liste grâce à la méthode append compte ici comme une seule opération.

  3. Estimer le coût $T_2(n)$ de l'algorithme correspondant à la fonction decompo_carres2.

    Considérez le calcul de la racine carrée comme comptant pour une seule opération au vu des tailles des entiers avec lesquels vous travaillez.

  4. Expliquer les observations permises avec le module default_timer.

Code de déblocage de la correction :

Complexité en moyenne

Voici un second algorithme renvoyant le booléen vrai si l'année entrée comme argument est une année bissextile et faux sinon :

def est_bissextile2(annee: int) -> bool:
    if annee % 4 == 0 :
        if (annee % 100 == 0) and (annee % 400 != 0) :
            return False
        else : # la condition précédente est fausse donc soit non divisible par 100, soit divisible par 400.
            return True
    else :
        return False         
  1. Déterminer le coût lorsque l'année entrée comme argument n'est pas divisible par 4.

  2. Déterminer le coût lorsque l'année entrée comme argument est divisible par 4.

    1. Quelle est la probabilité de considérer une année non divisible par 4 ?

    2. Quelle est la probabilité de considérer une année divisible par 4 ?

    3. On admet que le coût moyen est obtenu en faisant la moyenne de chaque coût possible pondéré par la probabilité de cette possibilité. On parle aussi de complexité moyenne.

      Montrer que le coût moyen est de $4.75$.

Code de déblocage de la correction :

QCM

Questions issues de la Banque Nationale de Sujets

Propriétaire des ressources ci-dessous : ministère de l'Éducation nationale et de la jeunesse, licence CC BY SA NC

Voici une sélection de questions issues de la banque nationale de sujets, répondez à ces questions (attention, cette sélection n'est pas exhaustive).

On considère la fonction Python suivante, qui prend en argument une liste L et renvoie le maximum des éléments de la liste :

def recherche_maximum(L):
	max = L[0]
    for i in range(len(L)):
		if L[i] > max:
			max = L[i]
	return max                            

On note $n$ la taille de la liste.
Quelle est la complexité en nombre d’opérations de l’algorithme ?

Réponses :

A- constante, c’est-à-dire ne dépend pas de $n$.

B- linéaire, c’est-à-dire de l’ordre de $n$.

C- quadratique, c’est-à-dire de l’ordre de $n^2$.

D- cubique, c’est-à-dire de l’ordre de $n^3$.

On conçoit un algorithme permettant de déterminer la valeur maximale parmi une liste quelconque de valeurs comparables.
Pour une liste de 100 valeurs, le nombre minimal de comparaisons que doit effectuer cet algorithme est :

Réponses :

A- 7.

B- 99.

C- 200.

D- 10000.

On exécute le script suivant :

for i in range(n):
    for j in range(i):
        print('NSI')                            

Combien de fois le mot NSI est-il affiché ?

Réponses :

A- $n^2$

B- $(n+1)^2$

C- $1+2+...+(n-1)$

D- $1+2+...+(n-1)+n$

Soit L une liste de $n$ nombres réels ($n$ entier naturel non nul).
On considère l'algorithme suivant, en langage Python, calculant la moyenne des éléments de L.

M = 0
for k in range(n):
    M = M + L[k]
M = M/n                             

Si le nombre $n$ de données double alors le temps d'exécution de ce script :

Réponses :

A- reste le même.

B- double aussi.

C- est multiplié par $n$.

D- est multiplié par 4.

Quel code, parmi les quatre proposés ci-dessous, s'exécute en un temps linéaire en $n$ (c'est-à-dire avec un temps d'exécution majoré par $A\times n + B$ où $A$ et $B$ sont deux constantes) ?

Réponses :

A-

for i in range(n//2):
	for j in range(i+1,n):
		print('hello')            

B-

for i in range(n):
	print('hello')            

C-

L = [i+j for i in range(n) for j in range(n)]
for x in L:
	print('hello')            

D-

for i in range(n//2):
	for j in range(n//2):
		print('hello')            

Lors de l'exécution du code suivant, combien de fois l'opération a = 2*a sera-t-elle effectuée ?

a = 1
cpt = 1
while cpt < 8: 
    a = 2*a
    cpt = cpt+1            

Réponses :

A- 0.

B- 1.

C- 7.

D- 8.

Quelle valeur permet de compléter l’affirmation suivante : « Le nombre d’opérations nécessaires pour rechercher un élément séquentiellement dans un tableau de longueur $n$ est de l’ordre de … » ?

Réponses :

A- 1.

B- $n$.

C- $n^2$.

D- $n^3$.

La fonction ci-dessous compte le nombre d'occurrences d'un élément x dans une liste L :

def compteur(L, x):
	n = 0
	for item in L:
		if item == x:
			n = n + 1
	return n             

Comment évolue le temps d'exécution d'un appel de cette fonction si on prend comme argument une liste deux fois plus grande ?

Réponses :

A- c'est le même temps d'exécution.

B- le temps d'exécution est à peu près doublé.

C- le temps d'exécution est à peu près quadruplé.

D- impossible de le prévoir, cela dépend aussi de l'argument x.

On définit une fonction de calcul de la moyenne d'une liste de nombres :

def moyenne(L):
	s = 0
	n = len(L)
	for x in L:
		s = s + x
	return s/n             

Combien cette fonction utilise-t-elle d'additions et de divisions pour calculer la moyenne d'une liste de 7 nombres ?

Réponses :

A- 7.

B- 8.

C- 9.

D- 10.

On considère le code suivant de recherche d'une valeur dans une liste :

def search(x, y):
    # x est la valeur à chercher
    # y est une liste de valeurs
    for i in range(len(y)):
        if x == y[i]:
            return i
    return None            

Quel est le coût de cet algorithme ?

Réponses :

A- constant.

B- logarithmique.

C- linéaire.

D- quadratique.

Autres QCM

Un algorithme est en complexité quadratique. Codé en python, son exécution pour des données de taille 1000 prend 10 millisecondes.
Si l'on fournit des données de taille 2000 au programme, on peut s'attendre à un temps d'exécution d'environ :

Réponses :

A- 10 millisecondes.

B- 20 millisecondes.

C- 40 millisecondes.

D- 80 millisecondes.

On considère le code suivant permettant tester la présence d'une valeur dans une liste de nombres entiers :

def est_dans_liste(n:int, liste:list) -> bool:
    """
    Fonction renvoyant True si l'entier n est dans liste, False sinon
    """
    for i in range(len(liste)):
        if n == liste[i]:
            return True 
	return False            

Quel est le coût de cet algorithme ?

Réponses :

A- constant.

B- linéaire.

C- quadratique.

D- exponentiel.

Parmi les affirmations suivantes, laquelle est vraie ?

Réponses :

A- Deux algorithmes de même complexité effectuent exactement le même nombre d'opérations.

B- Plus le coût d'un algorithme est grand, plus précis est le résultat.

C- Dès qu'un algorithme porte sur une liste de taille $n$, sa complexité est au moins linéaire.

D- Le coût d'un algorithme permet d'évaluer un temps d'exécution.

On considère l'algorithme suivant :

def f(n: int) -> int:
    somme = 0
    for i in range(n**2):
        somme += i
	return somme             

Quel est le coût de cet algorithme ?

Réponses :

A- constant.

B- linéaire.

C- quadratique.

D- exponentiel.

On considère l'algorithme suivant portant sur une liste d'entiers de taille $n$ :

def f(liste: list) -> int:
    somme = 0
    n = len(liste)
    for i in range(n):
        somme += liste[i]
	return somme/n            

Si la taille des données est multipliée par 10 alors le temps d'exécution :

Réponses :

A- reste à peu après le même.

B- est à peu près multiplié par 10.

C- est à peu près multiplié par 100.

D- est à peu près multiplié par 1000.

Quel code, parmi les quatre proposés ci-dessous, s'exécute en un temps linéaire en $n$ ?

Réponses :

A-

for i in range(n//2):
    j = i+1
    print(i*j)            

B-

for i in range(n//2):
    for j in range(i+1, n):
        print(i*j)            

C-

liste = [i*j for i in range(n) for j in range(n)]
for x in liste:
    print(x)            

D-

for i in range(n//2):
    for j in range(n//2):
        print(i*j)            

On exécute le script suivant :

for i in range(n):
    for j in range(i+1, n):
        print('NSI')                            

Combien de fois le mot NSI est-il affiché ?

Réponses :

A- $n^2$

B- $(n-1)^2$

C- $1+2+...+(n-1)$

D- $1+2+...+(n-1)+n$

On exécute le script suivant portant sur un tableau tab à deux dimensions de taille $n\times n$, qui est symétrique (la diagonale principale), afin d'obtenir la somme de toutes les termes le composant :

1   somme = 0
2   for i in range(n):
3       for j in range(i+1, n):
4           somme += tab[i][j]
5       somme = 2*somme + tab[i][i]            

Combien de fois la ligne 5 sera exécutée ?

Réponses :

A- $1$ fois.

B- $n-i$ fois.

C- $n$ fois.

D- $n^2$ fois.

On exécute le script suivant portant sur un tableau tab à deux dimensions de taille $n\times n$, qui est symétrique (par rapport à la diagonale principale) afin d'obtenir la somme de toutes les termes le composant :

1   somme = 0
2   for i in range(n):
3       for j in range(i+1, n):
4           somme += tab[i][j]
5       somme = 2*somme + tab[i][i]            

Combien de fois la ligne 4 sera exécutée ?

Réponses :

A- $n$ fois.

B- $\dfrac{(n-1)n}{2}$ fois.

C- $\dfrac{(n+1)n}{2}$ fois.

D- $n^2$ fois.

Sitographie

Voici une liste de sites traitant de la complexité :

Exercices de renforcement

Déterminer la complexité, notée $T(n)$, de cet algorithme écrit en python qui permet d'obtenir le prix d'un produit TTC connaissant le prix Hors Taxe, la TVA (Taxes sur la Valeur Ajoutée) et une éventuelle réduction :

1 def prixTTC(HT: float, TVA: float, reduction: float) -> float:
2	TTC = HT*(1 + TVA/100)
3	TTC = TTC*(1 - reduction/100)
4	return TTC
Cliquer pour afficher la solution

La ligne 2 coûte 6 :

La ligne 3 coûte 6 :

Voici une fonction maxi écrite en python qui permet d'obtenir le maximum de trois entiers entrés comme argument :

1 def maxi(a: int, b: int, c: int) -> int:
2	if a > b and a > c:
3	    return a
4	elif b >= a and b > c: 
5       return b 
6   else: 
7       return c
  1. Quel est le coût (en temps) lorsque le maximum des arguments saisis est le premier saisi : a ?

  2. Quel est le coût (en temps) lorsque le maximum des arguments saisis est le deuxième saisi : b ?

  3. Quel est le coût (en temps) lorsque le maximum des arguments saisis est le dernier saisi : c ?

  4. Quel est le coût dans le pire des cas, c'est-à-dire dans le cas où le coût d'exécution est le plus grand ?
    Quel est le coût dans le meilleur cas ?

Cliquer pour afficher la solution

Les lignes 1, 3, 5 et 7 ne sont pas prises en compte dans le calcul du coût.

  1. Cas où le maximum des arguments saisis est le premier saisi : a :

    Seules les lignes 1, 2 et 3 sont exécutées dans ce cas.

    La ligne 2 coûte 7 :

    • l'accès en mémoire pour connaître les valeurs stockées dans les variables $a$, $b$ et $c$ coûtent chacune 1, soit un coût cumulé de 4,

    • les 2 tests de comparaison coûtent chacun 1, soit un coût additionné de 2,

    • l'opération élémentaire and sur les booléens coûte 1.

    Ainsi, le coût total est dans ce cas de 7.

  2. Cas où le maximum des arguments saisis est le deuxième saisi : b :

    Seules les lignes 1, 2, 4 et 5 sont exécutées dans ce cas.

    La ligne 2 coûte 7 (vu précédemment).

    La ligne 4 coûte 7 de la même manière :

    • l'accès en mémoire pour connaître les valeurs stockées dans les variables $a$, $b$ et $c$ coûtent chacune 1, soit un coût cumulé de 4,

    • les 2 tests de comparaison coûtent chacun 1, soit un coût additionné de 2,

    • l'opération élémentaire and sur les booléens coûte 1.

    Ainsi, le coût total est dans ce cas de 14.

  3. Cas où le maximum des arguments saisis est le dernier saisi : c :

    Seules les lignes 1, 2, 4, 6 et 7 sont exécutées dans ce cas.

    Les lignes 2 et 4 coûtent chacune 7 (vu précédemment).

    La ligne 6 coûte 0 : aucune affectation, ni accès en mémoire ni calcul ni est effectué.

    Ainsi, le coût total est dans ce cas de 14.

  4. Le coût dans le pire des cas est de 14.
    Le coût dans le meilleur cas est de 7.

Voici une fonction puissance écrite en python qui permet d'obtenir la puissance $n$-ième d'un entier $x$, $x$ et $n$ étant les deux arguments entiers de cette fonction :

1 def puissance(x: int, n: int) -> int:
2	res = 1
3	for i in range(n):
4	    res = res * x
5   return res 

Quel est le coût $T(n)$ de cet algorithme ?

Cliquer pour afficher la solution

La ligne 2 a un coût de 1 pour l'affectation.

La ligne 3 a un coût approximatif de 2 : 1 pour l'accès à $n$ et 1 pour l'incrémentation de $i$.

La ligne 4 a un coût de 4 : 2 affectations ($x$ et $res$), 1 multiplication et 1 affectation.

Les lignes 3 et 4 étant répétées $n$ fois, elles représentent un coût cumulé de $6n$.

Finalement, le coût total $T(n)$ est $6n+1$. On trouve un coût linéaire.

Voici un algorithme, très naïf, écrit en python qui permet d'obtenir la liste de tous les triplets Pythagoriciens $(a,b,c)$ de nombres compris entre 1 et $n$ tels que $a^2+b^2=c^2$ :

1 def liste_Pythagore(n: int) -> list:
2	L_res = []
3	for a in range(1, n+1):
4	    for b in range(1, n+1):
5           for c in range(1, n+1):
6               if a**2 + b**2 == c**2: 
7                   L_rec.append((a, b, c))
8   return L_res
  1. Quel est le coût de la ligne 6 ?

  2. Pour simplifier, vous allez désormais considérer que le coût des lignes 6 et 7 vaut toujours 9.
    Déterminer le coût des lignes suivantes ($a$ et $b$ sont considérés comme fixes) :

    5           for c in range(1, n+1):
    6               if a**2 + b**2 == c**2: 
    7                   L_rec.append((a, b, c))
  3. Avec la même simplification, déterminer le coût des lignes suivantes ($a$ est considéré comme fixe) :

    4	    for b in range(1, n+1):
    5           for c in range(1, n+1):
    6               if a**2 + b**2 == c**2: 
    7                   L_rec.append((a, b, c))
  4. En déduire le coût total $T(n)$ de cet algorithme.

Cliquer pour afficher la solution
  1. La ligne 6 a un coût de 8 : 3 pour l'accès aux variables $a$, $b$et $c$ ; 4 pour les opérations (3 multiplications et 1 addition) ; 1 pour la comparaison (test d'égalité)

  2. Le coût des lignes 6 et 7 est supposé valoir toujours 9.
    La lecture de la ligne 5 conduit à un coût supposé de 2 : 1 pour l'accès à $c$ et 1 pour la gestion du range(1,n+1).
    Ainsi, chaque itération a un coût cumulé supposé valoir toujours 11.

    Comme il y a $n$ itérations, le coût cumulé pour les lignes 5 à 7 est donc $11n$.

  3. Le coût des lignes 5 et 7 est supposé valoir toujours $11n$.
    La lecture de la ligne 4 conduit à un coût supposé de 2 : 1 pour l'accès à $b$ et 1 pour la gestion du range(1,n+1).
    Ainsi, chaque itération a un coût cumulé supposé valoir toujours $11n+2$.

    Comme il y a $n$ itérations, le coût cumulé pour les lignes 4 à 7 est donc $(11n+2)\times n$.

  4. Le coût des lignes 4 et 7 est supposé valoir toujours $(11n+2)\times n$.
    La lecture de la ligne 3 conduit à un coût supposé de 2 : 1 pour l'accès à $b$ et 1 pour la gestion du range(1,n+1).
    Ainsi, chaque itération a un coût cumulé supposé valoir toujours $(11n+2)\times n +2$.

    Comme il y a $n$ itérations, le coût cumulé pour les lignes 3 à 7 est donc $((11n+2)\times n +2)\times n$.

    À celà s'ajoutent le coût de la ligne 2 qui vaut 1 pour l'affectation, les autres lignes étant supposées sans coût.

    Finalement, le coût total $T(n)$ est $((11n+2)\times n +2)\times n +1$ soit $11n^3+2n^2+2n+1$. On trouve un coût polynomial : une puissance de 3.

Savoir faire et Savoir


Licence Creative Commons
Les différents auteurs mettent l'ensemble du site à disposition selon les termes de la licence Creative Commons Attribution - Pas d’Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International